package net.hpxn.gsmc.action;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import net.hpxn.gsmc.config.ConfigFactory;
import net.hpxn.gsmc.config.notifier.NotifierConfig;
import net.hpxn.gsmc.config.notifier.NotifierConfig.Addresses;
import net.hpxn.gsmc.config.notifier.NotifierConfig.Addresses.Address;
import net.hpxn.gsmc.core.server.Server;

import org.apache.log4j.Logger;

/*
 * This action will send out emails if the game server is not responding. 
 * This class depends upon the StatusUpdateAction. If the StatusUpdateAction 
 * is not enabled for the specific server then the server status will never be 
 * updated.
 */
public class UptimeEmailNotifier extends BaseAction {
	protected Logger log;

	private static int MAX_ERROR_COUNT = 3;
	private static volatile HashMap<String, StatusInfo> statusTable;
	private static NotifierConfig notifierConfig;

	private String serverName;

	public UptimeEmailNotifier() {
		log = Logger.getLogger( this.getClass().getCanonicalName() );

		if ( notifierConfig == null ) {
			try {
				notifierConfig = ConfigFactory.getNotifierConfig();
			} catch ( Exception e ) {
				log.error( "Error loading UptimeEmailNotifier configuration: "
								+ e.getMessage() );
			}
		}
		MAX_ERROR_COUNT = Integer.valueOf( notifierConfig.getMaxErrorCount() );
	}

	@Override
	public void run() {
		if ( statusTable == null ) {
			statusTable = new HashMap<String, StatusInfo>();
		}

		serverName = getEvent().getServer().getName();
		if ( statusTable.get( serverName ) == null ) {
			statusTable.put( serverName, new StatusInfo( serverName ) );
		}

		while ( isRunning() ) {
			StatusInfo wStatusInfo = statusTable.get( serverName );
			Server wServer = getEvent().getServer();

			if ( wServer.isUp() ) {
				if ( wStatusInfo.getDownSince() != null ) {
					sendEmails( wStatusInfo, serverName + " has RECOVERED - "
									+ Calendar.getInstance().getTime(),
						getUpMessageBody( wStatusInfo ) );

				}
				wStatusInfo.setDownSince( null );
				wStatusInfo.setErrorCount( 0 );
			} else {
				int wErrorCount = wStatusInfo.getErrorCount();
				wStatusInfo.setErrorCount( wErrorCount + 1 );
				wStatusInfo.setLastErrorOccurred( Calendar.getInstance() );

				potentiallySendEmails( wStatusInfo );
			}

			try {
				Thread.sleep( 30000 );
			} catch ( InterruptedException e ) {
				// k.
			}
		}
	}

	private void potentiallySendEmails( StatusInfo pStatusInfo ) {
		Calendar wLastEmailSentPlus = pStatusInfo.getLastEmailSent();
		int wEmailDelayMins =
			Integer.valueOf( notifierConfig.getEmailDelayMins() );
		if ( wLastEmailSentPlus != null ) {
			wLastEmailSentPlus.add( Calendar.MINUTE, wEmailDelayMins );
		}

		if ( pStatusInfo.getErrorCount() >= MAX_ERROR_COUNT ) {
			pStatusInfo.setErrorCount( 0 );
			if ( pStatusInfo.getLastEmailSent() == null
							|| Calendar.getInstance()
											.after( wLastEmailSentPlus ) ) {
				if ( pStatusInfo.getDownSince() == null ) {
					pStatusInfo.setDownSince( Calendar.getInstance() );
				}
				sendEmails( pStatusInfo, serverName + " is DOWN - "
								+ Calendar.getInstance().getTime(),
					getDownMessageBody( pStatusInfo ) );
			} else {
				log.warn( serverName + " is DOWN! Reseting counter..." );
			}

		} else {
			log.warn( serverName + " is DOWN! " + pStatusInfo.getErrorCount()
							+ "/" + MAX_ERROR_COUNT );
		}
	}

	private void sendEmails( final StatusInfo pStatusInfo,
					final String pSubject, final String pBody ) {
		log.info( "Sending notification e-mail for server: "
						+ getEvent().getServer().getName() );
		pStatusInfo.setLastEmailSent( Calendar.getInstance() );

		final String host = notifierConfig.getSmtpServer();
		final int port = Integer.valueOf( notifierConfig.getSmtpPort() );
		final String username = notifierConfig.getSmtpUsername();
		final String password = notifierConfig.getSmtpPassword();

		Properties props = new Properties();
		props.put( "mail.smtp.auth", "true" );
		props.put( "mail.smtp.socketFactory.port", port );
		props.put( "mail.smtp.port", port );
		props.put( "mail.smtp.host", host );

		if ( "tls".equalsIgnoreCase( notifierConfig.getSmtpEncType() ) ) {
			props.put( "mail.smtp.starttls.enable", "true" );
		}

		if ( "ssl".equalsIgnoreCase( notifierConfig.getSmtpEncType() ) ) {
			props.put( "mail.smtp.socketFactory.class",
				"javax.net.ssl.SSLSocketFactory" );
		}

		final Session session =
			Session.getDefaultInstance( props, new javax.mail.Authenticator() {
				protected PasswordAuthentication getPasswordAuthentication() {
					return new PasswordAuthentication( username, password );
				}
			} );

		new Thread( new Runnable() {
			public void run() {
				for ( Addresses wAddresses : notifierConfig.getAddresses() ) {
					for ( Address wAddress : wAddresses.getAddress() ) {
						Message wMsg = new MimeMessage( session );
						try {
							wMsg.setFrom( new InternetAddress( "gsmc" ) );
							wMsg.setRecipients( Message.RecipientType.TO,
								InternetAddress.parse( wAddress.getValue() ) );
							wMsg.setSubject( pSubject );
							wMsg.setText( pBody );

							Transport.send( wMsg );
						} catch ( Exception e ) {
							log.error( "Error sending notification e-mail: "
											+ e.getMessage() );
						}
					}
				}
			}
		} ).start();
	}

	private String getDownMessageBody( StatusInfo pStatusInfo ) {
		StringBuilder wSb = new StringBuilder();

		wSb.append( pStatusInfo.getServerName() + " is DOWN!\n\n" );
		wSb.append( "Down since: " + pStatusInfo.getDownSince().getTime()
						+ "\n" );
		wSb.append( "Last Error: "
						+ pStatusInfo.getLastErrorOccurred().getTime() );

		return wSb.toString();
	}

	private String getUpMessageBody( StatusInfo pStatusInfo ) {
		StringBuilder wSb = new StringBuilder();

		wSb.append( pStatusInfo.getServerName() + " has RECOVERED.\n\n" );
		wSb.append( "Down since: " + pStatusInfo.getDownSince().getTime()
						+ "\n" );
		wSb.append( "Last Error: "
						+ pStatusInfo.getLastErrorOccurred().getTime() );

		return wSb.toString();
	}

	class StatusInfo {
		private int errorCount = 0;
		private Calendar lastEmailSent;
		private Calendar lastErrorOccurred;
		private Calendar downSince;
		private String serverName;

		public StatusInfo( String pServerName ) {
			this.serverName = pServerName;
		}

		public int getErrorCount() {
			return errorCount;
		}

		public void setErrorCount( int errorCount ) {
			this.errorCount = errorCount;
		}

		public Calendar getLastEmailSent() {
			return lastEmailSent;
		}

		public void setLastEmailSent( Calendar lastEmailSent ) {
			this.lastEmailSent = lastEmailSent;
		}

		public Calendar getLastErrorOccurred() {
			return lastErrorOccurred;
		}

		public void setLastErrorOccurred( Calendar lastErrorOccurred ) {
			this.lastErrorOccurred = lastErrorOccurred;
		}

		public Calendar getDownSince() {
			return downSince;
		}

		public void setDownSince( Calendar downSince ) {
			this.downSince = downSince;
		}

		public String getServerName() {
			return serverName;
		}

		public void setServerName( String serverName ) {
			this.serverName = serverName;
		}
	}
}
